﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using CSEngine;

namespace Server
{
    public class GameServer: TcpServer
    {
        public enum GameType { HUMAN_AI, AI_AI, UNSPECIFIED }

        private TcpClient remotePlayer;
        private Thread gameThread;
        private GameType type;
        private AI myAi;
        private Game g;
        private Move? remoteMove;
        private bool in_progress;
        private bool display;

        public event ServerInputDelegate InvalidClientInput;

        public GameServer()
            : base()
        {
            remotePlayer = null;
            in_progress = false;
            type = GameType.UNSPECIFIED;
            ClientInput += GameServer_ClientInput;
            InvalidClientInput += TcpServer.DoNothing;
            display = true;
            gameThread = null;
            myAi = GetDefaultAI();
        }

        private void GameServer_ClientInput(object sender, ServerInputArgs e)
        {
            String[] words = e.Message.Trim().Split(' ','\t','\n');
            foreach (String word in words)
                ProcessInput(word, e);
        }

        private void ProcessInput(string word, ServerEventArgs args)
        {
            Expression expr;
            if (!Expression.TryParse(word, out expr))
            {
                InvalidClientInput.Invoke(this, new ServerInputArgs(args.Client, word));
                return;
            }

            switch (expr.Type)
            {
                case Expression.ExprType.COMMAND:
                    ProcessCommand((Command)expr.Value, args.Client);
                    break;
                case Expression.ExprType.COMMENT:
                    break;
                case Expression.ExprType.MOVE:
                    ProcessMove((Move)expr.Value);
                    break;
                default:
                    throw new NotSupportedException("Invalid Expression Type");
            }
        }

        // This method should ONLY be called by the Game thread!
        // DO NOT call this method from within class GameServer
        private Move WaitForRemoteMove(object sender, PlayerTurnArgs e)
        {
            Move result;
            remoteMove = null;

            // Wait for remoteMove to be updated by the server thread
            while (!remoteMove.HasValue)
                Thread.Sleep(Settings.SleepDelay);

            result = remoteMove.GetValueOrDefault();
            remoteMove = null;
            return result;
        }

        private void ProcessMove(Move mv)
        {
            if (remoteMove.HasValue)
                throw new InvalidOperationException("remoteMove already has a value");

            remoteMove = mv;
        }

        private void ProcessCommand(Command com, TcpClient sender)
        {
            switch (com.CommandName)
            {
                case Command.CommandType.AI_AI:
                    throw new NotImplementedException("AI-AI Command Not Yet Implemented");
                case Command.CommandType.DISPLAY:
                    display = !display;
                    break;
                case Command.CommandType.EASY:
                    myAi = new EasyAI();
                    break;
                case Command.CommandType.EXIT:
                    throw new NotImplementedException("EXIT Command Not Yet Implemented");
                case Command.CommandType.HARD:
                    myAi = GetHardAI();
                    break;
                case Command.CommandType.HUMAN_AI:
                    StartHumanAIGame(sender);
                    break;
                case Command.CommandType.MEDIUM:
                    myAi = GetMediumAI();
                    break;
                case Command.CommandType.UNDO:
                    // TODO - This does not appear to work?
                    // TODO - Error checking - "g" may be null
                    g.UndoLastMove();
                    break;
                default:
                    throw new NotSupportedException("Command Not Supported");
            }
        }

        private void StartHumanAIGame(TcpClient sender)
        {
            if (in_progress)
                throw new InvalidOperationException("Game Already In Progress");

            in_progress = true;
            type = GameType.HUMAN_AI;
            remotePlayer = sender;

            g = new Game();
            g.BindPlayer1Turn(WaitForRemoteMove);
            g.BindPlayer2Turn(myAi.GetNextMove);
            g.BoardUpdated += DisplayBoard;
            g.GameOver += NotifyGameOver;
            gameThread = new Thread(new ThreadStart(g.Play));
            gameThread.Start();
        }

        private void NotifyGameOver(object sender, GameOverArgs e)
        {
            string msg = "Game Over - ";
            if (e.Draw)
                msg += "Draw";
            else
                msg += e.Winner.Name + " Wins";

            if (Display)
            {
                Console.WriteLine(msg);
            }
            SendLine(remotePlayer, msg);
            g = null;
            in_progress = false;
            type = GameType.UNSPECIFIED;
            remotePlayer = null;
        }

        private void DisplayBoard(object sender, BoardUpdatedArgs e)
        {
            if (Display)
            {
                Console.WriteLine(e.Board.ToString());
                SendLine(remotePlayer, e.Board.ToString().Replace("\n", "\r\n"));
            }
        }

        public AI AI
        {
            get { return myAi; }
            set { myAi = value; }
        }

        public TcpClient RemotePlayer
        {
            get { return remotePlayer; }
        }

        /// <summary>
        /// Gets a value indicating whether there is a game in progress
        /// </summary>
        public bool InProgress
        {
            get { return in_progress; }
        }

        /// <summary>
        /// Check if this server is currently displaying the game state
        /// </summary>
        public bool Display
        {
            get { return display; }
        }

        /// <summary>
        /// Get the type of game that is in progress on the server
        /// </summary>
        public GameType Type
        {
            get { return type; }
        }

        /// <summary>
        /// Get an instance the default AI used by this GameServer
        /// </summary>
        public static AI GetDefaultAI()
        {
            return GetEasyAI();
        }

        /// <summary>
        /// Get an instance of the easy AI used by this GameServer
        /// </summary>
        public static AI GetEasyAI()
        {
            return new EasyAI();
        }

        /// <summary>
        /// Get an instance of the medium AI used by this GameServer
        /// </summary>
        public static AI GetMediumAI()
        {
            throw new NotImplementedException("Medium AI Not Yet Implemented");
            //return new MediumAI();
        }

        /// <summary>
        /// Get an instance of the hard AI used by this GameServer
        /// </summary>
        public static AI GetHardAI()
        {
            throw new NotImplementedException("Hard AI Not Yet Implemented");
            //return new HardAI();
        }
    }
}
